package falcon.tool;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import falcon.client.FalconClient;
import falcon.client.FalconClientPool;
import falcon.client.FalconClientProxy;
import falcon.common.Errno;
import falcon.common.FalconConfig;
import falcon.common.TextFile;
import falcon.protocol.Packet;
import falcon.protocol.impl.AckPacket;
import jline.console.ConsoleReader;
import jline.console.completer.Completer;

public final class FalconShell {

	public static void main(String[] args) {		
		if (args.length < 2) {
			System.out.println("Usage: FalconShell.jar <conf_path> <user>");
			return;
		}
		String path = args[0];
		if (!TextFile.exists(path)) {
			System.out.println(String.format("Error: path not found;path=%s", path));
			return;
		}
		FalconConfig config = new FalconConfig(path);
		List<String> hostList = config.getHostList();
		if (hostList.isEmpty()) {
			System.out.println(String.format("Error: host not found"));
			return;
		}
		String user = args[1];
		FalconShell shell = new FalconShell(config, user);
		int errno = shell.init();
		if (errno == Errno.SUCCESS) {
			shell.listenInput();
		} else {
			System.out.println("Error: 登陆失败");
		}
		shell.destory();
	}
	
	public int init() {
		final int TRY_COUNT = 3;
		int errno = Errno.SUCCESS;
		try {
			m_reader = new ConsoleReader();
			Completer completer = new FsCompleter(m_ctx.root);
			m_reader.addCompleter(completer);
			for (int i = 0; i < TRY_COUNT; i ++) {
				m_password = readPassword();
				if (m_password == null) {
					errno = Errno.COM_CATCH_EXCEPTION;
					break;
				}
				if ("".equals(m_password)) {
					System.out.println("Error: 密码不能为空");
					errno = Errno.VERIF_PASSWORD_EMPTY;
					continue;
				}
				FalconClientProxy proxy = m_pool.get();
				errno = proxy.login(m_user, m_password);
				if (errno == Errno.SUCCESS) {
					break;
				}
				System.out.println("Error: 身份验证失败");
			}
			if (errno != Errno.SUCCESS) {
				return errno;
			}
			
		} catch (Exception e) {
			errno = Errno.COM_CATCH_EXCEPTION;
			e.printStackTrace();
		}
		return errno;
	}
	
	public void listenInput() {
		try {
			System.out.println("Info: 欢迎使用 FalconShell");
			new HelpHandler().process(m_ctx, null);
			while (true) {
				String prompt = m_ctx.status == FsStatus.Wait ? "" : ">";
				String line = m_reader.readLine(prompt);
				if ("".equals(prompt) || line == null || line.isEmpty()) {
					continue;
				}
				FsNode.parse(line, m_ctx);
				if (m_ctx.status == FsStatus.Exit) {
					System.out.println("正在退出 Bye~");
					break;
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public void destory() {
		m_pool.destory();
	}
	
	private FalconShell(FalconConfig config, String user) {
		m_config = config;
		m_user = user;
		m_ctx = new FsCtx(this);
		m_pool = new FalconClientPool(m_config);
	}
	
	private String readPassword() {
		String password;
		try {
			password = m_reader.readLine("Password: ", '*');
		} catch (IOException e) {
			password = null;
			e.printStackTrace();
		}
		return password;
	}
	
	private enum FsStatus {
		Exit,
		Again,
		Wait,
	}
	
	private static final class Keyword {
		// object
		public static final String BASH = "bash";
		public static final String CONF = "conf";
		public static final String DIR = "dir";
		public static final String FILE = "file";
		public static final String FLOW = "flow";
		public static final String FUNC = "func";
		public static final String HOST = "host";
		public static final String SYS = "sys";
		public static final String TASK = "task";
		// action
		public static final String ADD = "add";
		public static final String CREATE = "create";
		public static final String DELETE = "delete";
		public static final String DOWNLOAD = "download";
		public static final String EXECUTE = "execute";
		public static final String EXIT = "exit";
		public static final String HELP = "help";
		public static final String KILL = "kill";
		public static final String LOCALHOST = "localhost";
		public static final String LOGIN = "login";
		public static final String LOGOUT = "logout";
		public static final String MOVE = "move";
		public static final String UPLOAD = "upload";
		public static final String SET = "set";
		public static final String SHOW = "show";
	}
	
	private interface FsHandler {
		public int process(FsCtx ctx, String[] args);
	}
	
	private static final class FsNode {
		public String keyword;
		public FsHandler handler;
		public List<FsNode> nexts;
		
		public static void add(FsNode root, FsHandler handler, String... keywords) {
			if (root.nexts == null) {
				root.nexts = new ArrayList<FsNode>();
			}
			add(0, root, handler, keywords);
		}
		
		public static int complete(String line, int offset, FsNode root, List<CharSequence> candidates) {
			List<String> list = new ArrayList<String>();
			FsNode current = root;
			int offsetX = offset;
			for (int i = 0; i < offset; i ++) {
				char ch = line.charAt(i);
				if (ch == ' ' || ch == '\t') {
					continue;
				}
				char quote = '\0';
				int start = i;
				if (ch == '\'' || ch == '\"') {
					quote = ch;
					i ++;
				}
				// 过滤 引号
				for (; i < offset; i ++) {
					ch = line.charAt(i);
					if (quote == '\0' && (ch == ' ' || ch == '\t')) {
						break;
					} else if (quote == ch) {
						break;
					}
				}
				String prefix = line.substring(start, i);
				// 对光标之前的字符串进行 完全匹配
				if (i < offset) {
					boolean found = false;
					for (int x = 0; x < current.nexts.size(); x ++) {
						FsNode node = current.nexts.get(x);
						// 如果发现匹配的关键字，则再次循环遍历下一个关键字
						if (node.keyword.equals(prefix)) {
							found = true;
							current = node;
							break;
						}
					}
					// 不存在匹配的关键字时，直接退出查找
					// 不用修改光标偏移量
					if (!found) {
						return offsetX;
					}
					continue;
				}
				// 对当前光标指定的字符串进行 前缀匹配
				for (int x = 0; x < current.nexts.size(); x ++) {
					FsNode node = current.nexts.get(x);
					String keyword = node.keyword;
					if (!keyword.startsWith(prefix)) {
						continue;
					}
					list.add(keyword);
				}
				// list empty 时，说明不存在匹配的前缀，直接退出查找
				if (list.isEmpty()) {
					return offsetX;
				}
				// 返回前缀匹配的所有关键字
				// 扣除前缀偏移量
				offsetX -= prefix.length();
				break;
			}
			if (list.isEmpty()) {
				for (int x = 0; x < current.nexts.size(); x ++) {
					String keyword = current.nexts.get(x).keyword;
					candidates.add(keyword);
				}
			} else {
				candidates.addAll(list);
			}
			return offsetX;
		}
		
		public static int parse(String line, FsCtx ctx) {
			String[] args = splitLine(line);
			if (args == null || args.length < 1) {
				return Errno.SUCCESS;
			}	
			List<FsNode> nodeList = new ArrayList<FalconShell.FsNode>();
			FsNode current = ctx.root;
			nodeList.add(current);
			for (int i = 0; i < args.length; i ++) {
				String value = args[i];
				FsNode fix = null;
				FsNode var = null;
				for (int x = 0; x < current.nexts.size(); x ++) {
					FsNode node = current.nexts.get(x);
					String keyword = node.keyword;
					char ch = keyword.charAt(0);
					if (ch == '<' || ch == '[') {
						if (var == null) {
							var = node;
						}
					} else if (keyword.equals(value)) {
						fix = node;
						break;
					}
				}
				if (fix != null) {
					current = fix;
				} else if (var != null) {
					current = var;
				} else {
					break;
				}
				nodeList.add(current);
			}
			// 从右向左查找最接近的关键字对应的 handler 方法
			FsHandler handler = null;
			for (int i = nodeList.size() - 1; i > -1; i --) {
				FsNode node = nodeList.get(i);
				if (node.handler != null) {
					handler = node.handler;
					break;
				}
			}
			if (handler == null) {
				handler = new HelpHandler();
			}
			int errno;
			try {
				errno = handler.process(ctx, args);
			} catch (Exception e) {
				errno = Errno.COM_CATCH_EXCEPTION;
				e.printStackTrace();
			}
			return errno;
		}
		
		private static void add(int idx, FsNode node, FsHandler handler, String... keywords) {
			if (idx >= keywords.length) {
				node.handler = handler;
				return;
			}
			String keyword = keywords[idx];
			List<FsNode> list = node.nexts;
			FsNode found = null;
			for (int i = 0; i < list.size(); i ++) {
				FsNode sn = list.get(i);
				if (keyword.equals(sn.keyword)) {
					found = sn;
					break;
				}
			}
			if (found == null) {
				found = new FsNode();
				found.keyword = keyword;
				found.handler = null;
				found.nexts = new ArrayList<FsNode>();
				list.add(found);
			}
			add(idx + 1, found, handler, keywords);
		}
		
		private static String[] splitLine(String line) {
			List<String> list = new ArrayList<String>();
			char quote = '\0';
			int i = 0;
			int begin = 0;
			for (; i < line.length(); i ++) {
				boolean cut = false;
				char ch = line.charAt(i);
				if (quote == '\0') {
					if (ch == '\'' || ch == '"') {
						quote = ch;
						begin = i + 1;
					} else if (ch == ' ' || ch == '\t') {
						cut = true;
					}
				} else if (quote == ch) {
					quote = '\0';
					cut = true;
				}
				if (i + 1 >= line.length()) {
					i = line.length();
					cut = true;
				}
				if (cut) {
					if (i - begin > 0) {
						list.add(line.substring(begin, i));
					}
					begin = i + 1;
				}
			}
			if (quote != '\0') {
				String str = line.substring(begin, i);
				System.out.println(String.format("Error: 字符串 \"%s\" 必须以 %c 结尾", str, quote));
				return null;
			}
			String[] args = new String[list.size()];
			return list.toArray(args);
		}
	}
	
	private static final class FsCompleter implements Completer {
		public FsCompleter(FsNode root) {
			m_root = root;
		}

		@Override
		public int complete(String line, int offset, List<CharSequence> candidates) {
			return FsNode.complete(line, offset, m_root, candidates);
		}

		private FsNode m_root;
	}
	
	private static final class HelpHandler implements FsHandler {
		@Override
		public int process(FsCtx ctx, String[] args) {
			StringBuilder sb = new StringBuilder(256);
			sb.append("Info: 使用【Tab】键查支持的【输入值】\n");
			sb.append("Info: 指令基本语法格式为【对象】【动作】【参数】\n");
			sb.append("Info: 例如【查看帮助】对应的指令为 sys help [action]\n");
			sb.append("Info: 例如【本机BASH】对应的指令为 bash localhost <cmd> [args]\n");
			sb.append("Info: 其中【<xx>】表示【必填参数】，【[xx]】表示【可选参数】\n");
			System.out.println(sb.toString());
			if (args == null) {
				return Errno.SUCCESS;
			}
			String arg0 = args[0];
			if (Keyword.BASH.equals(arg0)) {
				return new BashHelpHandler().process(ctx, null);
			} else if (Keyword.HOST.equals(arg0)) {
				return new HostHelpHandler().process(ctx, null);
			}
			return Errno.SUCCESS;
		}
	}
	
	private static final class BashHelpHandler implements FsHandler {
		@Override
		public int process(FsCtx ctx, String[] args) {
			StringBuilder sb = new StringBuilder(256);
			sb.append("bash: 在远程主机或服务主机上执行shell命令");
			if (args == null) {
				System.out.println(sb.toString());
				return Errno.SUCCESS;
			}
			
			System.out.println(sb.toString());
			return Errno.SUCCESS;
		}
	}
	
	private static final class BashExecHandler implements FsHandler {
		@Override
		public int process(final FsCtx ctx, String[] args) {
			int port;
			String host, user;
			if (Keyword.LOCALHOST.equals(args[1])) {
				host = Keyword.LOCALHOST;
				port = ctx.getConfig().getSshPort();
				user = ctx.getConfig().getSshUser();
			} else {
				// user@host:port
				String remote = "";
				if (args[1] != null) {
					remote = args[1];
				}
				int x = remote.indexOf('@');
				if (x < 0) {
					return Errno.COM_ARGS_INVALID;
				}
				user = remote.substring(0, x);
				int y = remote.indexOf(':');
				if (y < x) {
					return Errno.COM_ARGS_INVALID;
				}
				host = remote.substring(x + 1, y);
				port = Integer.parseInt(remote.substring(y + 1));
			}
			if (host == null || "".equals(host)) {
				System.out.println("Error: host 不能为空");
				return Errno.COM_ARGS_INVALID;
			}
			if (port < 1) {
				System.out.println("Error: port 不能为空");
				return Errno.COM_ARGS_INVALID;
			}
			if (user == null || "".equals(user)) {
				System.out.println("Error: user 不能为空");
				return Errno.COM_ARGS_INVALID;
			}
			StringBuilder sb = new StringBuilder(256);
			for (int i = 2; i < args.length; i ++) {
				sb.append(args[i]);
				sb.append(' ');
			}
			String cmd = sb.toString().trim();
			if ("".equals(cmd)) {
				System.out.println("Error: cmd 不能为空");
				return Errno.COM_ARGS_INVALID;
			}
			
			FalconClientProxy proxy = ctx.getProxy();
			FalconClient.Callback callback = new FalconClient.Callback() {
				@Override
				public void onComplete(int errno, Packet packet) {
					ctx.status = FsStatus.Again;
					String desc = packet == null ? "" : ((AckPacket)packet).desc;
					if (errno != Errno.SUCCESS) {
						System.out.println(String.format("Error: 执行失败;errno=%d;desc=%s", errno, desc));
						return;
					}
					System.out.println(String.format("Info: 执行成功\n%s", desc));
				}
			};
			ctx.status = FsStatus.Wait;
			proxy.execBash(host, port, user, cmd, FalconClientProxy.SIMPLE_EXPIRE, callback);
			return Errno.SUCCESS;
		}
	}
	
	private static final class ConfHelpHandler implements FsHandler {
		@Override
		public int process(FsCtx ctx, String[] args) {
			System.out.println("ConfHelpHandler 功能暂未实现");
			return Errno.SUCCESS;
		}
	}
	
	private static final class DirHelpHandler implements FsHandler {
		@Override
		public int process(FsCtx ctx, String[] args) {
			System.out.println("Warn: DirHelpHandler 功能暂未实现");
			return Errno.SUCCESS;
		}
	}
	
	private static final class FileHelpHandler implements FsHandler {
		@Override
		public int process(FsCtx ctx, String[] args) {
			System.out.println("Warn: FileHelpHandler 功能暂未实现");
			return Errno.SUCCESS;
		}
	}
	
	private static final class FlowExecuteHandler implements FsHandler {
		@Override
		public int process(FsCtx ctx, String[] args) {
			if (Keyword.EXECUTE.equals(args[1])) {
				
			} else {
				
			}
			return Errno.SUCCESS;
		}
	}
	
	private static final class FlowHelpHandler implements FsHandler {
		@Override
		public int process(FsCtx ctx, String[] args) {
			System.out.println("Warn: FlowHelpHandler 功能暂未实现");
			return Errno.SUCCESS;
		}
	}
	
	private static final class FuncExecuteHandler implements FsHandler {
		@Override
		public int process(FsCtx ctx, String[] args) {
			// TODO Auto-generated method stub
			return Errno.SUCCESS;
		}
	}
	
	private static final class FuncHelpHandler implements FsHandler {
		@Override
		public int process(FsCtx ctx, String[] args) {
			System.out.println("Warn: FuncHelpHandler 功能暂未实现");
			return Errno.SUCCESS;
		}
	}
	
	private static final class HostHandler implements FsHandler {
		@Override
		public int process(FsCtx ctx, String[] args) {
			FalconClientProxy proxy = ctx.getProxy();
			int errno = Errno.COM_ARGS_INVALID;
			String host = args[2];
			int port = Integer.parseInt(args[3]);
			String user = args[4];
			String password = ctx.readPassword();
			if (password == null || "".equals(password)) {
				System.out.println("Error: 密码不能为空");
				return errno;
			}
			if (Keyword.ADD.equals(args[1])) {
				errno = proxy.addHost(host, port, user, password);
			} else if (Keyword.DELETE.equals(args[1])) {
				errno = proxy.removeHost(host, port, user, password);
			}
			return errno;
		}
	}
	
	private static final class HostHelpHandler implements FsHandler {
		@Override
		public int process(FsCtx ctx, String[] args) {
			System.out.println("Warn: HostHelpHandler 功能暂未实现");
			return Errno.SUCCESS;
		}
	}
	
	private static final class TaskHelpHandler implements FsHandler {
		@Override
		public int process(FsCtx ctx, String[] args) {
			System.out.println("Warn: TaskHelpHandler 功能暂未实现");
			return Errno.SUCCESS;
		}
	}
	
	private static final class SysHandler implements FsHandler {
		@Override
		public int process(FsCtx ctx, String[] args) {
			if (Keyword.EXIT.equals(args[1])) {
				ctx.status = FsStatus.Exit;
			} else {
				System.out.println("SysHandler");
			}
			return Errno.SUCCESS;
		}
	}
	
	private static final class SysHelpHandler implements FsHandler {
		@Override
		public int process(FsCtx ctx, String[] args) {
			System.out.println("SysHelpHandler");
			return Errno.SUCCESS;
		}
	}
	
	private static final class FsCtx {
		public FsCtx(FalconShell shell) {
			this.shell = shell;
			status = FsStatus.Again;
			root = new FsNode();
			FsNode.add(root, new HelpHandler());
			
			FsNode.add(root, new BashHelpHandler(), Keyword.BASH);
			FsNode.add(root, new BashHelpHandler(), Keyword.BASH, Keyword.HELP);
			FsNode.add(root, new BashExecHandler(), Keyword.BASH, Keyword.LOCALHOST, "<cmd>");
			FsNode.add(root, new BashExecHandler(), Keyword.BASH, Keyword.LOCALHOST, "<cmd>", "[args]");
			FsNode.add(root, new BashExecHandler(), Keyword.BASH, "<user@host:port>", "<cmd>");
			FsNode.add(root, new BashExecHandler(), Keyword.BASH, "<user@host:port>", "<cmd>", "[args]");
			
			FsNode.add(root, new ConfHelpHandler(), Keyword.CONF);
			FsNode.add(root, new ConfHelpHandler(), Keyword.CONF, Keyword.HELP);
			FsNode.add(root, new ConfHelpHandler(), Keyword.CONF, Keyword.SET, "<key>", "<value>");
			FsNode.add(root, new ConfHelpHandler(), Keyword.CONF, Keyword.SHOW, "<key>");
			
			FsNode.add(root, new DirHelpHandler(), Keyword.DIR);
			FsNode.add(root, new DirHelpHandler(), Keyword.DIR, Keyword.CREATE, "<path>");
			FsNode.add(root, new DirHelpHandler(), Keyword.DIR, Keyword.DELETE, "<path>");
			FsNode.add(root, new DirHelpHandler(), Keyword.DIR, Keyword.HELP);
			FsNode.add(root, new DirHelpHandler(), Keyword.DIR, Keyword.MOVE, "<src_path>", "<dst_path>");
			FsNode.add(root, new DirHelpHandler(), Keyword.DIR, Keyword.SHOW, "<path>");
			
			FsNode.add(root, new FileHelpHandler(), Keyword.FILE);
			FsNode.add(root, new FileHelpHandler(), Keyword.FILE, Keyword.DELETE, "<path>");
			FsNode.add(root, new FileHelpHandler(), Keyword.FILE, Keyword.DOWNLOAD, "<src_path>", "<dst_path>");
			FsNode.add(root, new FileHelpHandler(), Keyword.FILE, Keyword.HELP);
			FsNode.add(root, new FileHelpHandler(), Keyword.FILE, Keyword.MOVE, "<src_path>", "<dst_path>");
			FsNode.add(root, new FileHelpHandler(), Keyword.FILE, Keyword.SHOW, "<path>");
			FsNode.add(root, new FileHelpHandler(), Keyword.FILE, Keyword.UPLOAD, "<src_path>", "<dst_path>");
			
			FsNode.add(root, new FlowHelpHandler(), Keyword.FLOW);
			FsNode.add(root, new FlowHelpHandler(), Keyword.FLOW, Keyword.DELETE);
			FsNode.add(root, new FlowExecuteHandler(), Keyword.FLOW, Keyword.EXECUTE, "<name>");
			FsNode.add(root, new FlowExecuteHandler(), Keyword.FLOW, Keyword.EXECUTE, "<name>", "[args]");
			FsNode.add(root, new FlowHelpHandler(), Keyword.FLOW, Keyword.HELP);
			FsNode.add(root, new FlowHelpHandler(), Keyword.FLOW, Keyword.SET);
			FsNode.add(root, new FlowHelpHandler(), Keyword.FLOW, Keyword.SHOW);
			
			FsNode.add(root, new FuncHelpHandler(), Keyword.FUNC);
			FsNode.add(root, new FuncHelpHandler(), Keyword.FUNC, Keyword.DELETE);
			FsNode.add(root, new FuncExecuteHandler(), Keyword.FUNC, Keyword.EXECUTE, "<name>");
			FsNode.add(root, new FuncExecuteHandler(), Keyword.FUNC, Keyword.EXECUTE, "<name>", "[args]");
			FsNode.add(root, new FuncHelpHandler(), Keyword.FUNC, Keyword.HELP);
			FsNode.add(root, new FuncHelpHandler(), Keyword.FUNC, Keyword.SET);
			FsNode.add(root, new FuncHelpHandler(), Keyword.FUNC, Keyword.SHOW);
			
			FsNode.add(root, new HostHelpHandler(), Keyword.HOST);
			FsNode.add(root, new HostHandler(), Keyword.HOST, Keyword.ADD, "<host>", "<port>", "<user>");
			FsNode.add(root, new HostHandler(), Keyword.HOST, Keyword.DELETE, "<host>", "<port>", "<user>");
			FsNode.add(root, new HostHelpHandler(), Keyword.HOST, Keyword.HELP);
			
			
			FsNode.add(root, new TaskHelpHandler(), Keyword.TASK);
			FsNode.add(root, new TaskHelpHandler(), Keyword.TASK, Keyword.KILL);
			FsNode.add(root, new TaskHelpHandler(), Keyword.TASK, Keyword.HELP);
			FsNode.add(root, new TaskHelpHandler(), Keyword.TASK, Keyword.SHOW);
			
			FsNode.add(root, new SysHelpHandler(), Keyword.SYS);
			FsNode.add(root, new SysHandler(), Keyword.SYS, Keyword.EXIT);
			FsNode.add(root, new SysHandler(), Keyword.SYS, Keyword.LOGIN, "<user>");
			FsNode.add(root, new SysHandler(), Keyword.SYS, Keyword.LOGOUT);
			FsNode.add(root, new SysHelpHandler(), Keyword.SYS, Keyword.HELP);
			FsNode.add(root, new HelpHandler(), Keyword.SYS, Keyword.HELP, "[action]");
		}
		
		public FalconClientProxy getProxy() {
			return shell.m_pool.get();
		}
		
		public FalconConfig getConfig() {
			return shell.m_config;
		}
		
		public String readPassword() {
			return shell.readPassword();
		}
		
		public FalconShell shell;
		public FsStatus status;
		public FsNode root;
	}
	
	private String m_user;
	private String m_password;
	private FalconConfig m_config;
	private FalconClientPool m_pool;
	private ConsoleReader m_reader;
	private FsCtx m_ctx;
}
